/*
 * libid3tag - ID3 tag manipulation library
 * Copyright (C) 2000-2004 Underbit Technologies, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * $Id: frame.c,v 1.15 2004/01/23 09:41:32 rob Exp $
 */

# include "global.h"

# include <stdlib.h>
# include <string.h>

# ifdef HAVE_ASSERT_H
#  include <assert.h>
# endif

# include "id3tag.h"
# include "frame.h"
# include "frametype.h"
# include "compat.h"
# include "field.h"
# include "render.h"
# include "parse.h"
# include "util.h"

static
int valid_idchar(char c)
{
	return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
}

/*
 * NAME:	frame->validid()
 * DESCRIPTION:	return true if the parameter string is a legal frame ID
 */
int id3_frame_validid(char const *id)
{
	return id &&
		   valid_idchar(id[0]) &&
		   valid_idchar(id[1]) &&
		   valid_idchar(id[2]) &&
		   valid_idchar(id[3]);
}

/*
 * NAME:	frame->new()
 * DESCRIPTION:	allocate and return a new frame
 */
struct id3_frame *id3_frame_new(char const *id) {
	struct id3_frametype const *frametype;
	struct id3_frame *frame;
	unsigned int i;

	if (!id3_frame_validid(id))
		return 0;

	frametype = id3_frametype_lookup(id, 4);
	if (frametype == 0) {
		switch (id[0]) {
			case 'T':
				frametype = &id3_frametype_text;
				break;

			case 'W':
				frametype = &id3_frametype_url;
				break;

			case 'X':
			case 'Y':
			case 'Z':
				frametype = &id3_frametype_experimental;
				break;

			default:
				frametype = &id3_frametype_unknown;
				if (id3_compat_lookup((char *)id, 4))
					frametype = &id3_frametype_obsolete;
				break;
		}
	}

	//frame = malloc(sizeof(*frame) + frametype->nfields * sizeof(*frame->fields));
	frame = malloc(sizeof(*frame));
	if (frame) {
		frame->id[0] = id[0];
		frame->id[1] = id[1];
		frame->id[2] = id[2];
		frame->id[3] = id[3];
		frame->id[4] = 0;

		frame->description       = frametype->description;
		frame->refcount          = 0;
		frame->flags             = frametype->defaultflags;
		frame->group_id          = 0;
		frame->encryption_method = 0;
		frame->encoded           = 0;
		frame->encoded_length    = 0;
		frame->decoded_length    = 0;
		frame->nfields           = frametype->nfields;
		frame->fields			 = malloc(frametype->nfields * sizeof(*frame->fields));
		//frame->fields            = (union id3_field *) &frame[1];
		//addr = (size_t)&frame->fields;
		//addr += 4;
		//frame->fields            = (union id3_field *) addr;

		for (i = 0; i < frame->nfields; ++i)
			id3_field_init(&frame->fields[i], frametype->fields[i]);
	}

	return frame;
}

void id3_frame_delete(struct id3_frame *frame)
{
	assert(frame);

	if (frame->refcount == 0) {
		unsigned int i;

		for (i = 0; i < frame->nfields; ++i)
			id3_field_finish(&frame->fields[i]);

		if (frame->encoded)
			free(frame->encoded);

		if (frame->fields)
			free(frame->fields);
		free(frame);
	}
}

/*
 * NAME:	frame->addref()
 * DESCRIPTION:	add an external reference to a frame
 */
void id3_frame_addref(struct id3_frame *frame)
{
	assert(frame);

	++frame->refcount;
}

/*
 * NAME:	frame->delref()
 * DESCRIPTION:	remove an external reference to a frame
 */
void id3_frame_delref(struct id3_frame *frame)
{
	assert(frame && frame->refcount > 0);

	--frame->refcount;
}

/*
 * NAME:	frame->field()
 * DESCRIPTION:	return a pointer to a field in a frame
 */
union id3_field *id3_frame_field(struct id3_frame const *frame, unsigned int index) {
	assert(frame);

//	return (index < frame->nfields) ? (union id3_field *)&frame->fields[index] : 0;
	return (index < frame->nfields) ? &frame->fields[index] : 0;
}

static
struct id3_frame *obsolete(char const *id, id3_byte_t const *data,
						   id3_length_t length) {
	struct id3_frame *frame;

	frame = id3_frame_new(ID3_FRAME_OBSOLETE);
	if (frame) {
		if (id3_field_setframeid(&frame->fields[0], id) == -1 ||
				id3_field_setbinarydata(&frame->fields[1], data, length) == -1)
			goto fail;
	}

	if (0) {
fail:
		if (frame) {
			id3_frame_delete(frame);
			frame = 0;
		}
	}

	return frame;
}

static
struct id3_frame *unparseable(char const *id, id3_byte_t const **ptr,
							  id3_length_t length, int flags,
							  int group_id, int encryption_method,
							  id3_length_t decoded_length) {
	struct id3_frame *frame = 0;
	id3_byte_t *mem;

	mem = malloc(length ? length : 1);
	if (mem == 0)
		goto fail;

	frame = id3_frame_new(id);
	if (frame == 0)
		free(mem);
	else {
		memcpy(mem, *ptr, length);

		frame->flags             = flags;
		frame->group_id          = group_id;
		frame->encryption_method = encryption_method;
		frame->encoded           = mem;
		frame->encoded_length    = length;
		frame->decoded_length    = decoded_length;
	}

	if (0) {
fail:
		;
	}

	*ptr += length;

	return frame;
}

static
int parse_data(struct id3_frame *frame,
			   id3_byte_t const *data, id3_length_t length)
{
	enum id3_field_textencoding encoding;
	id3_byte_t const *end;
	unsigned int i;

	encoding = ID3_FIELD_TEXTENCODING_ISO_8859_1;

	end = data + length;

	for (i = 0; i < frame->nfields; ++i) {
		if (id3_field_parse(&frame->fields[i], &data, end - data, &encoding) == -1)
			return -1;
	}

	return 0;
}

/*
 * NAME:	frame->parse()
 * DESCRIPTION:	parse raw frame data according to the specified ID3 tag version
 */
struct id3_frame *id3_frame_parse(id3_byte_t const **ptr, id3_length_t length, unsigned int version) {
	struct id3_frame *frame = 0;
	id3_byte_t const *id, *end, *data;
	id3_length_t size, decoded_length = 0;
	int flags = 0, group_id = 0, encryption_method = 0;
	struct id3_compat const *compat = 0;
	id3_byte_t *mem = 0;
	char xid[4];

	id  = *ptr;
	end = *ptr + length;

	if (ID3_TAG_VERSION_MAJOR(version) < 4) {
		switch (ID3_TAG_VERSION_MAJOR(version)) {
			case 2:
				if (length < 6)
					goto fail;

				compat = id3_compat_lookup((char *)id, 3);

				*ptr += 3;
				size  = id3_parse_uint(ptr, 3);

				if (size > end - *ptr)
					goto fail;
				end = *ptr + size;
				break;

			case 3:
				if (length < 10)
					goto fail;

				compat = id3_compat_lookup((char *)id, 4);

				*ptr += 4;
				size  = id3_parse_uint(ptr, 4);
				flags = id3_parse_uint(ptr, 2);

				if (size > end - *ptr)
					goto fail;

				end = *ptr + size;

				if (flags & (ID3_FRAME_FLAG_FORMATFLAGS & ~0x00e0)) {
					frame = unparseable((char *)id, ptr, end - *ptr, 0, 0, 0, 0);
					goto done;
				}

				flags =
					((flags >> 1) & ID3_FRAME_FLAG_STATUSFLAGS) |
					((flags >> 4) & (ID3_FRAME_FLAG_COMPRESSION |
									 ID3_FRAME_FLAG_ENCRYPTION)) |
					((flags << 1) & ID3_FRAME_FLAG_GROUPINGIDENTITY);

				if (flags & ID3_FRAME_FLAG_COMPRESSION) {
					if (end - *ptr < 4)
						goto fail;
					decoded_length = id3_parse_uint(ptr, 4);
				}

				if (flags & ID3_FRAME_FLAG_ENCRYPTION) {
					if (end - *ptr < 1)
						goto fail;
					encryption_method = id3_parse_uint(ptr, 1);
				}

				if (flags & ID3_FRAME_FLAG_GROUPINGIDENTITY) {
					if (end - *ptr < 1)
						goto fail;
					group_id = id3_parse_uint(ptr, 1);
				}
				break;
			default:
				goto fail;
		}

		/* canonicalize frame ID for ID3v2.4 */

		if (compat && compat->equiv)
			id = compat->equiv;
		else if (ID3_TAG_VERSION_MAJOR(version) == 2) {
			xid[0] = 'Y';
			xid[1] = id[0];
			xid[2] = id[1];
			xid[3] = id[2];

			id = xid;

			flags |= ID3_FRAME_FLAG_TAGALTERPRESERVATION | ID3_FRAME_FLAG_FILEALTERPRESERVATION;
		}
	} else { /* ID3v2.4 */
		if (length < 10)
			goto fail;

		*ptr += 4;
		size  = id3_parse_syncsafe(ptr, 4);
		flags = id3_parse_uint(ptr, 2);

		if (size > end - *ptr)
			goto fail;

		end = *ptr + size;

		if (flags & (ID3_FRAME_FLAG_FORMATFLAGS & ~ID3_FRAME_FLAG_KNOWNFLAGS)) {
			frame = unparseable((char *)id, ptr, end - *ptr, flags, 0, 0, 0);
			goto done;
		}

		if (flags & ID3_FRAME_FLAG_GROUPINGIDENTITY) {
			if (end - *ptr < 1)
				goto fail;

			group_id = id3_parse_uint(ptr, 1);
		}

		if ((flags & ID3_FRAME_FLAG_COMPRESSION) &&
				!(flags & ID3_FRAME_FLAG_DATALENGTHINDICATOR))
			goto fail;

		if (flags & ID3_FRAME_FLAG_ENCRYPTION) {
			if (end - *ptr < 1)
				goto fail;

			encryption_method = id3_parse_uint(ptr, 1);
		}

		if (flags & ID3_FRAME_FLAG_DATALENGTHINDICATOR) {
			if (end - *ptr < 4)
				goto fail;

			decoded_length = id3_parse_syncsafe(ptr, 4);
		}
	}

	data = *ptr;
	*ptr = end;

	/* undo frame encodings */

	if ((flags & ID3_FRAME_FLAG_UNSYNCHRONISATION) && end - data > 0) {
		mem = malloc(end - data);
		if (mem == 0)
			goto fail;

		memcpy(mem, data, end - data);

		end  = mem + id3_util_deunsynchronise(mem, end - data);
		data = mem;
	}

	if (flags & ID3_FRAME_FLAG_ENCRYPTION) {
		frame = unparseable((char *)id, &data, end - data, flags,
							group_id, encryption_method, decoded_length);
		goto done;
	}

	if (flags & ID3_FRAME_FLAG_COMPRESSION) {
		id3_byte_t *decomp;

		decomp = id3_util_decompress(data, end - data, decoded_length);
		if (decomp == 0)
			goto fail;

		if (mem)
			free(mem);

		data = mem = decomp;
		end  = data + decoded_length;
	}

	/* check for obsolescence */

	if (compat && !compat->equiv) {
		frame = obsolete((char *)id, data, end - data);
		goto done;
	}

	/* generate the internal frame structure */

	frame = id3_frame_new((char *)id);
	if (frame) {
		frame->flags    = flags;
		frame->group_id = group_id;

		if (compat && compat->translate) {
			if (compat->translate(frame, compat->id, data, end - data) == -1)
				goto fail;
		} else {
			if (parse_data(frame, data, end - data) == -1)
				goto fail;
		}
	}

	if (0) {
fail:
		if (frame) {
			id3_frame_delete(frame);
			frame = 0;
		}
	}

done:
	if (mem)
		free(mem);
	return frame;
}

static
id3_length_t render_data(id3_byte_t **ptr,
						 union id3_field *fields, unsigned int length)
{
	id3_length_t size = 0;
	enum id3_field_textencoding encoding;
	unsigned int i;

	encoding = ID3_FIELD_TEXTENCODING_ISO_8859_1;

	for (i = 0; i < length; ++i)
		size += id3_field_render(&fields[i], ptr, &encoding, i < length - 1);

	return size;
}

/*
 * NAME:	frame->render()
 * DESCRIPTION:	render a single, complete frame
 */
id3_length_t id3_frame_render(struct id3_frame const *frame,
							  id3_byte_t **ptr, int options)
{
	id3_length_t size = 0, decoded_length, datalen;
	id3_byte_t *size_ptr = 0, *flags_ptr = 0, *data = 0;
	int flags;

	assert(frame);

	if ((frame->flags & ID3_FRAME_FLAG_TAGALTERPRESERVATION) ||
			((options & ID3_TAG_OPTION_FILEALTERED) &&
			 (frame->flags & ID3_FRAME_FLAG_FILEALTERPRESERVATION)))
		return 0;

	/* a frame must be at least 1 byte big, excluding the header */

	decoded_length = render_data(0, frame->fields, frame->nfields);
	if (decoded_length == 0 && frame->encoded == 0)
		return 0;

	/* header */

	size += id3_render_immediate(ptr, frame->id, 4);

	if (ptr)
		size_ptr = *ptr;

	size += id3_render_syncsafe(ptr, 0, 4);

	if (ptr)
		flags_ptr = *ptr;

	flags = frame->flags;

	size += id3_render_int(ptr, flags, 2);

	if (flags & (ID3_FRAME_FLAG_FORMATFLAGS & ~ID3_FRAME_FLAG_KNOWNFLAGS)) {
		size += id3_render_binary(ptr, frame->encoded, frame->encoded_length);
		if (size_ptr)
			id3_render_syncsafe(&size_ptr, (unsigned long)(size - 10), 4);

		return size;
	}

	flags &= ID3_FRAME_FLAG_KNOWNFLAGS;

	flags &= ~ID3_FRAME_FLAG_UNSYNCHRONISATION;
	if (options & ID3_TAG_OPTION_UNSYNCHRONISATION)
		flags |= ID3_FRAME_FLAG_UNSYNCHRONISATION;

	if (!(flags & ID3_FRAME_FLAG_ENCRYPTION)) {
		flags &= ~ID3_FRAME_FLAG_COMPRESSION;
		if (options & ID3_TAG_OPTION_COMPRESSION)
			flags |= ID3_FRAME_FLAG_COMPRESSION | ID3_FRAME_FLAG_DATALENGTHINDICATOR;
	}

	if (flags & ID3_FRAME_FLAG_GROUPINGIDENTITY)
		size += id3_render_int(ptr, frame->group_id, 1);
	if (flags & ID3_FRAME_FLAG_ENCRYPTION)
		size += id3_render_int(ptr, frame->encryption_method, 1);
	if (flags & ID3_FRAME_FLAG_DATALENGTHINDICATOR) {
		if (flags & ID3_FRAME_FLAG_ENCRYPTION)
			decoded_length = frame->decoded_length;
		size += id3_render_syncsafe(ptr, (unsigned long)decoded_length, 4);
	}

	if (ptr)
		data = *ptr;

	if (flags & ID3_FRAME_FLAG_ENCRYPTION)
		datalen = id3_render_binary(ptr, frame->encoded, frame->encoded_length);
	else {
		if (ptr == 0)
			datalen = decoded_length;
		else {
			datalen = render_data(ptr, frame->fields, frame->nfields);

			if (flags & ID3_FRAME_FLAG_COMPRESSION) {
				id3_byte_t *comp;
				id3_length_t complen;

				comp = id3_util_compress(data, datalen, &complen);
				if (comp == 0)
					flags &= ~ID3_FRAME_FLAG_COMPRESSION;
				else {
					*ptr = data;
					datalen = id3_render_binary(ptr, comp, complen);

					free(comp);
				}
			}
		}
	}

	/* unsynchronisation */

	if (flags & ID3_FRAME_FLAG_UNSYNCHRONISATION) {
		if (data == 0)
			datalen *= 2;
		else {
			id3_length_t newlen;

			newlen = id3_util_unsynchronise(data, datalen);
			if (newlen == datalen)
				flags &= ~ID3_FRAME_FLAG_UNSYNCHRONISATION;
			else {
				*ptr   += newlen - datalen;
				datalen = newlen;
			}
		}
	}

	size += datalen;

	/* patch size and flags */

	if (size_ptr)
		id3_render_syncsafe(&size_ptr, (unsigned long)(size - 10), 4);
	if (flags_ptr)
		id3_render_int(&flags_ptr, flags, 2);

	return size;
}
